/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 * */

// AmzServer - Tototoy's server model.
// Author: tonyjobmails@gmail.com (tonyhack).
//
// Define class UDPCrediblePatternSender.
//
// UDPCrediblePatternSender is a simple pattern witch achieves acknowledgement
// packet just like TCP, and this pattern can ensure the packet is ordered.
//

#ifndef __UDP__CREDIBLE__PATTERN__SENDER__H
#define __UDP__CREDIBLE__PATTERN__SENDER__H

#include "common.h"
#include "sort_queue_dynamic_buffer.h"
#include "serial_number_pool.h"
#include "condition.h"
#include "time_tick.h"
#include "packet_define.h"

namespace amz {

namespace udp_credible_pattern_sender {

// Acknowledgement sender sub message.
struct AckQueueElement {
  AckQueueElement() : resend_count_(0) {}
  AckQueueElement(uint8 resend_count, const TimeTick &time)
         : resend_count_(resend_count), timestamp_(time) {}

  const AckQueueElement& operator= (
          const AckQueueElement& element) {
    this->resend_count_ = element.resend_count_;
    this->timestamp_ = element.timestamp_;
    return *this;
  }

  uint8 resend_count_;
  TimestampMillisecond timestamp_;
};

// Some tools use to resend and delete.
class ResendCondition : public Condition<AckQueueElement> {
 public:
  ResendCondition(uint32 timeout_msecs, const TimeTick &time_now,
         bool modify_timestamp = true) : timeout_msecs_(timeout_msecs),
           timestamp_(time_now), modify_timestamp_(modify_timestamp) {}
  virtual ~ResendCondition() {}

  virtual bool Check(const AckQueueElement &element) const {
    if(element.timestamp_.Distance(this->timestamp_) >= this->timeout_msecs_) {
//      if(this->modify_timestamp_) {
//        element.timestamp_ = this->timestamp_;
//        ++element.resend_count_;
//      }
      return true;
    }
    return false;
  }

 private:
  uint32 timeout_msecs_;
  TimestampMillisecond timestamp_;
  // Flag of modifying.
  bool modify_timestamp_;
};

class DeleteCondition : public Condition<AckQueueElement> {
 public:
  DeleteCondition(uint8 resend_times) : resend_times_(resend_times) {}
  virtual ~DeleteCondition() {}

  virtual bool Check(const AckQueueElement &element) const {
    if(this->resend_times_ >= element.resend_count_)
      return true;
    return false;
  }

 private:
  uint8 resend_times_;
};

}  // namespace udp_credible_pattern_sender


class UDPCrediblePatternSender {
 public:
  typedef udp_credible_pattern_sender::AckQueueElement AckQueueElement;
  typedef SortQueueBufferDynamic<AckQueueElement>::ExecuteElement ExecuteElement;

  UDPCrediblePatternSender(uint32 max_serial_number,
         uint8 resend_times, uint32 timeout_msecs);
  virtual ~UDPCrediblePatternSender();

  // Check serial number is available.
  bool Available() const;
  // Apply a serial number, This do not check if there is any serial number available.
  void AskForSerialNumer(uint32 &serial_number, bool backward = true);
  // Backward serial number.
  inline void BackwardSerialNumer() { 
    this->serial_number_pool_.Set(true);
    this->serial_number_pool_.ShiftBackward();
  }

  // Calculate the size of packet.
  inline int CalculatePacketSize(int size) const { return size + packet::kCredibleUDPHeadSize; }
  // Packet a UDP credible packet and save it into ack_queue_.
  int Packet(uint32 serial_number, const char *data, int size,
         char *packet, int max_size, const TimeTick &time_now);

  // Ack packet arrives, release from ack_queue_.
  bool Acknowledge(uint32 serial_number);
  // Resend, resend is given by invoker.
  bool Resend(const TimeTick &time_now, ExecuteCallback<ExecuteElement> *resend);

 protected:
  uint8 resend_times_;
  uint32 timeout_msecs_;
  // Count queue, use to add a serial number to packet
  SerialNumberPool<bool> serial_number_pool_;
  // the queue to wait for acknowledge packet.
  SortQueueBufferDynamic<AckQueueElement> ack_queue_;
};

}  // namespace amz

#endif  // __UDP__CREDIBLE__PATTERN__SENDER__H
